//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#define _NO_INCLIST

#include <ddk.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "string.h"
#include <ctype.h>
#include <malloc.h>
#include <tfssys.h>
#include <misc.h>
#include <ktime.h>
#include <hash.h>
#include <llist.h>
#include <fat.h>
#include <abc.h>
#include <fatfile.h>
#include <limits.h>
#include "_sprintf.h"

const char illegal[] = ";+=[]',\"*\\<>/?:|",
villegal[] = "\"*\\<>/?:|";

FatDir::~FatDir()
{
    if (rootdirents)
        free(rootdirents);
    if (pFat)
        pFat->Release();
    if (pDiskAbc)
        pDiskAbc->Release();
    if (p_Sharedata)
        p_Sharedata->Release();
}

UInt32 FatDir::AddRef()
{
    return InterlockedIncrement((PInt32)&m_nRef);
}

UInt32  FatDir::Release()
{
    UInt32 Refs;

    Refs = InterlockedDecrement((PInt32)&m_nRef);
    if (Refs == 0) {
        delete this;
    }
    return Refs;
}

/*
 * ddirty()
 *    Mark a directory handle dirty
 *
 * Handles case of null handle, which means root
 */
void FatDir::
ddirty(void *handle)
{
    if (handle) {
        pDiskAbc->dirty_buf((struct buf *)handle, 0);
    }
    else {
        root_dirty = 1;
    }
}

/*
 * dfree()
 *    Free up a directory handle
 */
void FatDir::
dfree(void *handle)
{
    if (handle) {
        pDiskAbc->unlock_buf((struct buf *)handle);
    }
}

/*
 * dir_reload()
 *    Set up various tables, reload in root directory
 * param: flag : 1 reload, 0 init
 */
int FatDir::
dir_loadhash(UInt32 flag)
{
    /*
     * Main hash for directories
     */
    if (flag) {
        hash_dealloc(p_Sharedata->dirhash);
        hash_dealloc(p_Sharedata->procroot->n_files);
    }

    p_Sharedata->dirhash = hash_alloc(64);
    p_Sharedata->procroot->n_files = hash_alloc(16);

    if (!p_Sharedata->root_cluster) {
        if (rootdirents == 0) {
            DPRINTF(("ERROR:dir_init: rootdirents error\n"));
            return -1;
        }

        UInt64 uOffset = (p_Sharedata->bootb.BPB_ResvdSecCnt + \
                        p_Sharedata->bootb.BPB_NumFATs * \
                        p_Sharedata->bootb.BPB_FATSz16+ \
                        p_Sharedata->bpb_base) * p_Sharedata->secsz;
        ECode ec = ReadDevice(
                    p_Sharedata->pBlkDev,
                    uOffset, rootsize,
                    (PByte)rootdirents,
                    NULL, NULL);
        if (FAILED(ec)) {
            DPRINTF(("ERROR: Failed to Read from the offset:%d", uOffset));
            return -1;
        }
    }
    else {
        struct directory d;

        SETSTART(&d, p_Sharedata->root_cluster);
        if (flag) {//clear up fat32 root cluster
            pFat->free_clust(p_Sharedata->procroot->n_clust);
        }
        p_Sharedata->procroot->n_clust = pFat->alloc_clust(&d);
        if (p_Sharedata->procroot->n_clust == NULL)
            return -1;
    }

    return 0;
}

/*
 * dir_init()
 *    Set up various tables, read in root directory
 */
int FatDir::
dir_init(void *pFatDev, void *pAbcDev, class fatfs_sharedata *pSharedata)
{
    UInt32 x;

    ASSERT_DEBUG(pSharedata!=NULL, "fat_setlen: bad clust");
    p_Sharedata = pSharedata;
    p_Sharedata->AddRef();
    pFat = (class fat *)pFatDev;
    pFat->AddRef();
    pDiskAbc = (class abc *)pAbcDev;
    pDiskAbc->AddRef();

    /*
     * Get the root dir, fill it in
     */
    p_Sharedata->procroot = (struct node *)malloc(sizeof(struct node));
    if (p_Sharedata->procroot == 0) {
        return -1;
    }
    bzero(p_Sharedata->procroot, sizeof(struct node));
    p_Sharedata->procroot->n_type = T_DIR;
    p_Sharedata->procroot->n_refs = 1;            /* Always ref'ed */
    p_Sharedata->procroot->n_files = NULL;
    p_Sharedata->procroot->n_clust = NULL;
    p_Sharedata->procroot->n_dir = NULL;
    Ktfs_Init_Lock(&p_Sharedata->procroot->n_lock);

    if (!p_Sharedata->root_cluster) {
        /*
         * For FAT12 and FAT16:
         * The root dir is special in many ways.  Its directory
         * entries exist in data blocks which are not addressible
         * using the cluster values used by everyone else; in fact,
         * I can't find any reason to assume the size will even be
         * in multiples of clusters.  Therefore we read the entire
         * contentsinto a malloc()'ed buffer, and flush the buffer
         * back in units of sectors as needed.
         */
        rootsize = p_Sharedata->dirents * sizeof(struct directory);
        rootdirents = (struct directory *)malloc(rootsize);
        if (dir_loadhash(0) == -1)
            return -1;

        /*
         * This root directory pointer keys all the special cases
         * for dealing with a non-cluster allocated root.
         */
        //n->n_clust = 0;
        p_Sharedata->rootdir = p_Sharedata->procroot;
    }
    else {
        /*
         * The root directory is a normal cluster-based entity
         * in FAT32.  We'll always hold the root node in core,
         * but we need a pseudo directory entry in order to set
         * up the cluster chain.
         */
        if (dir_loadhash(0) == -1)
            return -1;
    }
    /*
     * Precalculate
     */
    cldirs = p_Sharedata->clsize/sizeof(struct directory);
    for (x = cldirs-1; x; x >>= 1) {
        clshift += 1;
    }

    return 0;
}

/*
 * map_filename()
 *    Convert between a UNIX-ish filename and its  counterpart
 *
 * We map an all-uppercase 8.3 name and all-lowercase 8.3 name
 * to short name.  Others we leave alone, and use VFAT support to handle it.
 *
 * Returns 0 on valid all-uppercase short filename, 1 on valid long filename,
 * 2 for an invalid filename, 3 on vaild all-lowercase 8.3 name.
 */
int
map_filename(char *file, char *f1, char *f2)
{
    char *p, c;
    int len = 0, ndot, allspace = 0;
    int fLongname = 0, fUppercase, fLowercase;

    /*
     * Scan filename for illegal characters.
     */
    for (p = file, ndot = 0; (c = (*p));++p,++len) {
        /*if char illegal in a short name
        * or a long name
        */
        if (strchr(villegal, c) || ((unsigned char)c < 0x20)) { // note: the variable 'c' is a signed char, and 0x80 is '-0'
            return(2);  //no ASCII byte (0x00-0x7F) can appear as part of any other character
        }               //also Chinese character, so c < 0x20 is illegal.

        /*
        * If char is illegal in a short name,
        * but OK in a long, use long format.
        */
        if ((unsigned char)c >= 0x80 || 0x20 == c || strchr(illegal, c)) {
            fLongname = 1;
        }

        if (c != 0x20) {
            allspace = 1;
        }

        /*
         * More than one dot in a filename oR The first char
         * is dot means it has to be represented as a long
         * filename.
         */
        if ('.' == c) {
            if (ndot++> 0 || p == file) {
                fLongname = 1;
            }
        }
    }
    if (allspace == 0) {
        return(2);//the file name is composed all by space character
    }

    if (len == 1 && (unsigned char)file[0] > 0x80)
        return (2); // single byte range from 0x81 to 0xFF is illegal.

    if (fLongname == 1) {
        return(1);
    }

    /*
     * Assemble 8.3 filename
     */
    p = f1;
    len = 0;
    fUppercase = 0;
    fLowercase = 0;
    strcpy(f1, "        ");

////linux & unix covert first "." to "_",but windows make this as a long filename
////
////     Map .<file> to _<file>
////     TBD: nuke this and depend on long filenames.
//
//    if (*file == '.') {
//        len += 1;
//        file += 1;
//        *p++ = '_';
//    }

    /*
     * Copy <file> part of <file>.<ext>
     */
    while ((c = *file++)) {
        c &= 0x7F;
        if (c == '.') {
            break;
        }
        if (isupper(c)) {
            fUppercase = 1;
        }
        if (islower(c)) {
            fLowercase = 1;
        }
        if (fUppercase && fLowercase) {
            return(1);
        }
        if (len++< 8) {
            *p++ = toupper(c);
        }
        else {
            return(1);
        }
    }

    /*
     * Now <ext> part, if any
     */
    strcpy(f2, "   ");
    if (c == '.') {        /* Extension found */
        p = f2;
        len = 0;
        while ((c = *file++)) {
            c &= 0x7F;
            if (isupper(c)) {
                fUppercase = 1;
            }
            if (islower(c)) {
                fLowercase = 1;
            }
            if (fUppercase && fLowercase) {
                return(1);
            }
            if (len++< 3) {
                *p++ = toupper(c);
            }
            else {
                return(1);
            }
        }
    }
    if (fLowercase) return(3);
    return(0);
}

/*
 * my_bcmp()
 *    Compare, binary style
 */
int
my_bcmp(const void *str1, const void *str2, unsigned int n)
{
    const char *p = (char *)str1;
    const char *q = (char *)str2;

    while (n--> 0) {
        if (*p++!= *q++) {
            return(1);
        }
    }
    return(0);
}

/*
 * This is the state kept for our callback to iterate VSE entries
 */
struct dirstate
{
    struct directory *st_dir1;    /* Directory to search */
    struct directory *st_dir2;    /*  ...overflow */
    int st_pos;            /* Current position */
    int st_ndir;            /* # entries available */
};

/*
 * next_dir()
 *    Callback function to provide next directory entry in VSE assembly
 */
static int __stdcall
next_dir(void *dp, void *statep, void *p)
{
    struct dirstate *state = (struct dirstate *)statep;

    /*
     * We've walked off the end of the first directory buffer;
     * if there's a second, use those.  Otherwise return failure.
     */
    if (state->st_pos >= state->st_ndir) {
        if (state->st_dir2 == NULL) {
            return(1);
        }
        bcopy(state->st_dir2 + (state->st_pos - state->st_ndir),
            (struct directory *)dp, sizeof(struct directory));
    }
    else {
        /*
         * Provide next dir entry
         */
        bcopy(state->st_dir1 + state->st_pos,
            (struct directory *)dp, sizeof(struct directory));
    }

    /*
     * Advance state and return success
     */
    state->st_pos += 1;
    return(0);
}

//cmp for vfat unicode
int
mywstrcasecmp(const wchar_t *str1, const wchar_t *str2)
{
    wchar_t w1, w2;

    for (;;) {
        /*
         * Get next pair of chars
         */
        w1 = *str1++;
        w2 = *str2++;

        /*
         * If match, return successful match on null termination
         */
        if (w1 == w2) {
            if (w1 == L'\0') {
                return(0);
            }
            continue;
        }

        /*
         * Map both from lower to upper case
         */
        if ((w1 >= L'a') && (w1 <= L'z')) {
            w1 = (w1 - L'a') + L'A';
        }
        if ((w2 >= L'a') && (w2 <= L'z')) {
            w2 = (w2 - L'a') + L'A';
        }

        /*
         * If they still don't match, return a mismatch
         */
        if (w1 != w2) {
            return(1);
        }

        /*
         * Otherwise just continue the loop
         */
    }
}

/*
 * search_vfat()
 *    Scan VSE entries to try and match the filename
 */
int FatDir::
search_vfat(struct directory *d, int ndir, struct directory *d2, wchar_t *filename)
{
    int x;
    UInt8 c;
    struct dirstate state;
    wchar_t *wbuf = NULL;

    wbuf = (wchar_t *)malloc(sizeof(wchar_t) * (VSE_MAX_NAME + 1));
    if (NULL == wbuf) {
        return -1;
    }

    for (x = 0; x < ndir;++x,++d) {
        /*
         * Null char in filename means no entries
         * beyond this point; search failed.
         */
        c = d->DIR_Name[0];
        if (c == 0) {
            free(wbuf);
            return(-1);
        }

        /*
         * Deleted file; ignore
         */
        if (c == DN_DEL) {
            continue;
        }

        if (dir_checkentry(d) != 0) {
            continue;
        }

        /*
         * If not a VSE, compare against the plain old fat name.
         */
        if (d->DIR_Attr != DA_VFAT) {
            pack_name(d, wbuf);
        }
        else {

            /*
             * Assemble VSE's to build up filename
             */
            state.st_dir1 = d+1;
            state.st_dir2 = d2;
            state.st_pos = 0;
            state.st_ndir = (ndir-x)-1;
            if (assemble_vfat_name(wbuf, d, next_dir, &state)) {
                continue;
            }

            /*
             * Get position after building from VSE's
            */
            x += state.st_pos;
            d += state.st_pos;

            /*
             * Return if matched
            */
        }
        if (!mywstrcasecmp(filename, wbuf)) {
            free(wbuf);
            return x;
        }
    }
    free(wbuf);
    return(-1);
}

/*
 * search_dir()
 *    Search an array of "struct directory"s for a filename
 *
 * If "f2" is NULL, this is a search of long-filename VFAT entries.
 * Otherwise it's a search of the 8.3 upper case names.
 *
 * Return value is an offsetinto the directory to the entry.  If
 * "d2" is non-NULL, this offset may be beyond the end of "d",
 * indicating an offset in "d2" instead.  On failure, -1 is returned.
 */
int FatDir::
search_dir(int ndir, struct directory *d, struct directory *d2,
    char *f1, char *f2, wchar_t *filename)
{
    /*
     * If we have f2 as well as f1 (filename f1.f2), we
     * search for traditional entries, otherwise long
     * filename ones
     */
    if (f2) {
        int x;
        struct directory *dirend = d + ndir;

        for (x = 0; d < dirend;++d,++x) {
            /*
             * If we reach a "never used" entry, there are
             * guaranteed to be no more beyond.
             */
            if (d->DIR_Name[0] == 0) {
                return(-1);
            }

            /*
             * Compare both filename parts
             */
            if (!my_bcmp(f1, d->DIR_Name, 8) &&
                !my_bcmp(f2, d->DIR_Ext, 3)) {
                return(x);
            }
        }
    }
    else {
        int off;

        /*
         * Search VSE entries.  The offset to the "real"
         * entry must always be beyond the starting point,
         * since at a minimum there must be the VSE,
         * followed by the short name "real" entry.
         */
        off = search_vfat(d, ndir, d2, filename);
        if (off >= 0) {
            return (off);
        }
    }
    return(-1);
}

/*
 * root_search()
 *    Search the root directory for an entry
 *
 * Return the index, or -1 if it can't be found.
 */
int FatDir::
root_search(char *f1, char *f2, struct directory *dp, wchar_t *filename)
{
    int x;

    x = search_dir(p_Sharedata->dirents, rootdirents, NULL, f1, f2, filename);
    if (x >= 0) {
        *dp = rootdirents[x];
    }
    return(x);
}

/*
 * dir_search()
 *    Like root_search(), but have to walk extents
 */
int FatDir::
dir_search(struct node *n, char *f1, char *f2, struct directory *dp, wchar_t *filename)
{
    void *handle = NULL, *handle2 = NULL;
    struct clust *c;
    int x;
    UInt32 cluster;
    struct directory *d, *d2 = NULL;

    /*
     * Walk each cluster, searching for this name
     */
    c = n->n_clust;
    for (cluster = 0; cluster < c->c_nclust;++cluster) {
        /*
         * Get next block.  If we already grabbed it
         * on the previous pass through, just use that
         * value.
         */
        if (d2) {
            d = d2;
            handle = handle2;
            d2 = NULL;
        }
        else {
            handle = pDiskAbc->find_buf(BOFF(c->c_clust[cluster]),
                CLSIZE, ABC_FILL);

            if (IS_ERR(handle)) {
                /* I/O error? */
                return PTR_ERR(handle);
            }

            d = (struct directory *)pDiskAbc->index_buf((struct buf *)handle, 0, CLSIZE);
            if (d == NULL) {
                pDiskAbc->unlock_buf((struct buf *)handle);
                return(-1);
            }
        }

        /*
         * If there's a following block, get it, too.
         * This is needed for long filename searches, where
         * the VSE's may straddle clusters.
         */
        if (!f2 && (cluster < c->c_nclust-1)) {
            handle2 = pDiskAbc->find_buf(BOFF(c->c_clust[cluster+1]),
                CLSIZE, ABC_FILL);

            if (!IS_ERR(handle2)) {
                d2 = (struct directory *)pDiskAbc->index_buf(
                     (struct buf *)handle2, 0, CLSIZE);
                if (d2 == NULL) {
                    pDiskAbc->unlock_buf((struct buf *)handle2);
                    return(-1);
                }
            }
            else {
                d2 = NULL;
            }
        }
        else {
            d2 = NULL;
        }

        /*
         * Search directory block
         */
        x = search_dir(cldirs, d, d2, f1, f2, filename);
        if (x >= 0) {
            if (x >= (int) cldirs) {
                *dp = d2[x-cldirs];
            }
            else {
                *dp = d[x];
            }
        }
        pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);

        if (x >= 0) {
            /*
             * If we have a lock on the following block
             * (and we're not going to use it because the
             * loop has terminated), release it now.
             */
            if (d2) {
                pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle2);
            }

            /*
             * This returns the correct value even when
             * x > cldirs (i.e., the entry was found
             * in handle2's cluster).  x has "+cldirs"
             * implicit in it, and "cluster" is one
             * short.
             */
            return(x + (cldirs * cluster));
        }
    }
    ASSERT_DEBUG(d2 == NULL, "dir_search: d2 != NULL");

    return(-1);
}

/*
* map_type()
*    Convert a fat attributeinto a file type
*/
int FatDir::
map_type(int fat_attr)
{
    if (fat_attr & DA_DIR)
        return(T_DIR);
    if (fat_attr & DA_HIDDEN)
        return(T_SYM);
    return(T_FILE);
}

/*
 * get_inum()
 *    Calculate inode value from containing dir and dir entry offset
 *
 * While not apparent, generating an inode number is both more important
 * and harder than it appears.  Because inode numbers are used by both
 * executable page caching as well as utilities like du, they need to
 * be unique, even after file close.
 *
 * So you can't use, say, the "struct node" address as it.  After the file
 * closes, the node gets free()'ed, and will pretty likely be reused for
 * a new open file.  But if it was, say, "du", then this would look like
 * the same file with a different name (hard links).  And thus "du" would
 * ignore the file, and report strangely small values.  This happened.
 *
 * Another "easy" fix is to use the storage address of the first block
 * of a file.  The problem is with zero-length files, which need an inode
 * number before they have any contents.  In UFS, the inode block is
 * allocated whether there's file contents or not, but this is not the
 * case in fat.  So we're left with the directory entry itself, which
 * is what we use to create an inode value with the needed properties.
 */
UInt32 FatDir::
get_inum(struct node *dir, int idx)
{
    UInt32 a;

    /*
     * Root isn't a "real" cluster, so just use 0 base on entry offset
     */
    if (dir == p_Sharedata->rootdir) {
        return (idx);
    }

    /*
     * Get the cluster address of the cluster holding this
     * directory entry.
     */
    a = pFat->get_clust(dir->n_clust, idx / cldirs);

    /*
     * Offset it by the entry index within that cluster
     */
    return ((a << clshift) | (idx % cldirs));
}

/*
 * node_search()
 *    Scan either a cluster-based directory or the root
 */
int FatDir::
node_search(struct node *n, char *fname, char *fname2, struct directory *d, wchar_t *filename)
{
    if (n == p_Sharedata->rootdir) {
        return(root_search(fname, fname2, d, filename));
    }
    else {
        return(dir_search(n, fname, fname2, d, filename));
    }
}

/*
 * check_direntry()
 *    Do checks on directory entries
 */
int FatDir::
dir_checkentry(struct directory *pd)
{
    int day, month, year;
    int sec, min, hur;

    if (pd->DIR_Attr & 0xC0) return -1;

    if ((pd->DIR_Attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) {
        struct dirVSE *dv = (struct dirVSE *)pd;
        if (dv->dv_sector != 0) return -1;
        if ((dv->dv_id & VSE_ID_MASK)>VSE_ID_MAX) return -1;
    }
    else {
        day = pd->DIR_CrtDate & 0x1F;
        month = (pd->DIR_CrtDate>>5) & 0x0F;
        year = (pd->DIR_CrtDate>>9) & 0x7F;

        if (pd->DIR_CrtDate && !(day > 0 && day < 32 &&
            month > 0 && month < 13 &&
            year >= 0 && year < 128)) {
            return -1;
        }

        sec = pd->DIR_CrtTime & 0x1F;
        min = (pd->DIR_CrtTime>>5) & 0x3F;
        hur = (pd->DIR_CrtTime>>11) & 0x1F;

        if (pd->DIR_CrtTime && !(sec >= 0 && sec < 30 &&
            min >= 0 && min < 60 &&
            hur >= 0 && hur < 24)) {
            return -1;
        }

        day = pd->DIR_WrtDate & 0x1F;
        month = (pd->DIR_WrtDate>>5) & 0x0F;
        year = (pd->DIR_WrtDate>>9) & 0x7F;

        if (pd->DIR_WrtDate && !(day > 0 && day < 32 &&
            month > 0 && month < 13 &&
            year >= 0 && year < 128)) {
            return -1;
        }

        sec = pd->DIR_WrtTime & 0x1F;
        min = (pd->DIR_WrtTime>>5) & 0x3F;
        hur = (pd->DIR_WrtTime>>11) & 0x1F;

        if (pd->DIR_WrtTime && !(sec >= 0 && sec < 30 &&
            min >= 0 && min < 60 &&
            hur >= 0 && hur < 24)) {
            return -1;
        }

        if ((pd->DIR_Name[0]&0xFF) == DN_DEL) {
            return -1;
        }

        if (pd->DIR_FstClusHI == 0 && pd->DIR_Start == 0 &&
            (pd->DIR_Attr & (DA_DIR | DA_VOLUME)) == DA_DIR) {
            if (memcmp(pd->DIR_Name, ".       ", 8) &&
                memcmp(pd->DIR_Name, "..      ", 8))
                return -1;
        }
    }
    return 0;
}

/*
 * dir_checkcontent()
 *    Check . and .. of a non-root dir
 */
int FatDir::
dir_checkcontent(struct directory *pd)
{
    void *handle;
    struct directory *ptmp;
    UInt32 cluster = pd->DIR_Start;

    cluster |= ((UInt32)pd->DIR_FstClusHI<<16);

    handle = pDiskAbc->find_buf(BOFF(cluster),
        CLSIZE, ABC_FILL);
    if (IS_ERR(handle)) {
        return -1;
    }

    ptmp = (struct directory *)pDiskAbc->index_buf((struct buf *)handle,
                                                     0, CLSIZE);
    if (ptmp == NULL) {
        pDiskAbc->unlock_buf((struct buf *)handle);
        return -1;
    }

    if (ptmp->DIR_Name[0] == 0x2E && ptmp->DIR_Name[1] == 0x20
        && ptmp->DIR_Name[2] == 0x20 && ptmp->DIR_Name[3] == 0x20
        && ptmp->DIR_Name[4] == 0x20 && ptmp->DIR_Name[5] == 0x20
        && ptmp->DIR_Name[6] == 0x20 && ptmp->DIR_Name[7] == 0x20
        && ptmp->DIR_Ext[0] == 0x20 && ptmp->DIR_Ext[1] == 0x20
        && ptmp->DIR_Ext[2] == 0x20) {
        ++ptmp;
        if (ptmp->DIR_Name[0] == 0x2E && ptmp->DIR_Name[1] == 0x2E
            && ptmp->DIR_Name[2] == 0x20 && ptmp->DIR_Name[3] == 0x20
            && ptmp->DIR_Name[4] == 0x20 && ptmp->DIR_Name[5] == 0x20
            && ptmp->DIR_Name[6] == 0x20 && ptmp->DIR_Name[7] == 0x20
            && ptmp->DIR_Ext[0] == 0x20 && ptmp->DIR_Ext[1] == 0x20
            && ptmp->DIR_Ext[2] == 0x20) {
            pDiskAbc->unlock_buf((struct buf *)handle);
            return 0;
        }
    }

    pDiskAbc->unlock_buf((struct buf *)handle);

    return -1;
}

/*
 * dir_look()
 *    Given dir node and filename, look up entry
 */
struct node * FatDir::
dir_look(struct node *n, wchar_t *file)
{
    const wchar_t *CurrentDir = L".";
    const wchar_t *ParentDir = L"..";
    char *fname, *fname2, f1[9], f2[4];
    struct directory d;
    int x;
    struct node *n2;
    wchar_t *fileName = NULL;
    char *GbFileName = NULL;

    if (wcscmp(file, CurrentDir) == 0) {
        ref_node(n);
        return n;
    }
    else if (wcscmp(file, ParentDir) == 0) {
        if (p_Sharedata->procroot == n) {
            return ERR_PTR(struct node *, ESRCH);
        }
        ref_node(n->n_dir);
        return n->n_dir;
    }
    else {
        fileName = (wchar_t *)malloc(sizeof(wchar_t) * MAX_FILE_NAME);
        if (NULL == fileName) {
            return ERR_PTR(struct node *, ENOMEM);
        }

        GbFileName = (char *)malloc(MAX_FILE_NAME * MB_LEN_MAX + 1);
        if (NULL == GbFileName) {
            free(fileName);
            return ERR_PTR(struct node *, ENOMEM);
        }

        wcscpy(fileName, file);
        /*
         * Wipe all '.' and ' ' at end of filename
         */
        dir_treatFileName(fileName);

#ifndef _win32
        WcsToMbs(GbFileName, fileName, MAX_FILE_NAME * MB_LEN_MAX + 1);
#else
        wcstombs(GbFileName, fileName, MAX_FILE_NAME * MB_LEN_MAX + 1);
#endif
        /*
         * Get a fat-ish version
         */
        switch (map_filename((char *)GbFileName, f1, f2)) {
        case 0:
            fname = f1;
            fname2 = f2;
            break;
        case 3:
            fname = f1;
            fname2 = f2;
            break;
        case 1:
            fname = (char *)GbFileName;
            fname2 = NULL;
            break;
        default:
            free(GbFileName);
            free(fileName);
            return ERR_PTR(struct node *, EINVAL);
        }
    }

    /*
     * Search dir; special case for root
     */
    x = node_search(n, fname, fname2, &d, fileName);

    free(GbFileName);
    free(fileName);

    /*
     * If not present, return failure
     */
    if (x < 0) {
        return ERR_PTR(struct node *, ESRCH);
    }

    /*
     * Check dir entry
     */
    if (dir_checkentry(&d) < 0) {
        return ERR_PTR(struct node *, EIO);
    }

    if ((d.DIR_Attr & DA_DIR) && dir_checkcontent(&d) != 0) {
        return ERR_PTR(struct node *, EIO);
    }

    /*
     * Have a dir entry, now try for the node itself.  File nodes
     * are stored under the parent directory node keyed by directory
     * offset.  All directories are hashed under a single table keyed
     * by starting cluster number.
     */
    if (d.DIR_Attr & DA_DIR) {
        n2 = (struct node *) hash_lookup(p_Sharedata->dirhash, START(&d));
    }
    else {
        n2 = (struct node *) hash_lookup(n->n_files, x);
    }

    /*
     * If we found it, add a reference and we're done
     */
    if (n2) {
        if(n2->n_sharemode == FileShareMode_Deny)
            return ERR_PTR(struct node *, EBUSY);
        ref_node(n2);
        return(n2);
    }

    return dir_getNodeByDirEntry(n, d, x);
}

/*
 * dir_empty()
 *    Tell if given directory is empty
 */
int FatDir::
dir_empty(struct node *n)
{
    struct clust *c;
    int cluster, x;
    void *handle;
    struct directory *d;

    /*
     * Trust us, root is *never* empty, and you can't remove
     * it anyway!
     */
    if (n == p_Sharedata->procroot) {
        return(0);
    }

    c = n->n_clust;
    for (cluster = 0; cluster < (int)c->c_nclust;++cluster) {
        /*
         * Get next block
         */
        handle = pDiskAbc->find_buf(BOFF(c->c_clust[cluster]),
            CLSIZE, ABC_FILL);
        if (IS_ERR(handle)) {
            /* I/O error? */
            return PTR_ERR(handle);
        }

        d = (struct directory *)pDiskAbc->index_buf((struct buf *)handle,
                                                     0, CLSIZE);
        if (d == NULL) {
            pDiskAbc->unlock_buf((struct buf *)handle);
            return(0);
        }
        /*
         * Search directory block
         */
        for (x = 0; x < (int) cldirs;++x,++d) {
            UInt8 c;

            /*
             * Look at first character
             */
            c = d->DIR_Name[0];

            /*
             * No more entries--we haven't seen a file,
             * so it's empty.
             */
            if (c == 0) {
                pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);
                return(1);
            }

            /*
             * "." and ".." are ignored, as are deleted files
             */
            if ((c == '.') || (c == DN_DEL)) {
                continue;
            }

            if ((d->DIR_Attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) {
                continue;
            }

            if (d->DIR_Attr == DA_VOLUME) {
                continue;
            }

            /*
             * Check dir entry
             */
            if (dir_checkentry(d) < 0) {
                continue; // bad dir entry
            }

            if ((d->DIR_Attr & DA_DIR) && dir_checkcontent(d) != 0) {
                continue; // bad dir content
            }

            /*
             * Oops.  Found a file.  Not empty.
             */
            pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);
            return(0);
        }
        pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);
    }
    return(1);
}

/*
 * get_dirent()
 *    Return pointer to struct directory on success or error code on failed,
 * also pass back handle
 *
 * The handle is NULL for root directory; otherwise it is a handle
 * to the block containing this directory entry.
 */
struct directory * FatDir::
get_dirent(struct node *n, UInt32 idx,void **handlep)
{
    struct directory *d;
    void *handle;
    UInt32 clnum;
    struct clust *c;

    if(n == NULL)
        return ERR_PTR(struct directory *, EPERM);
    /*
     * Root is pretty easy
     */
    if (n == p_Sharedata->rootdir) {
        if (idx >= p_Sharedata->dirents) {
            return ERR_PTR(struct directory *, EINVAL);
        }
        *handlep = 0;
        return(rootdirents + idx);
    }

    /*
     * Others require that we figure out which cluster
     * is needed and getit.
     */
    c = n->n_clust;
    clnum = idx / cldirs;
    if (clnum >= c->c_nclust) {
        return ERR_PTR(struct directory *, EINVAL);
    }
    handle =
        pDiskAbc->find_buf(BOFF(c->c_clust[clnum]), CLSIZE, ABC_FILL);
    if (IS_ERR(handle)) {
        *handlep = NULL;
        return ERR_PTR(struct directory *, PTR_ERR(handle));
    }

    d =(struct directory *)pDiskAbc->index_buf((struct buf *)handle, 0,
                    CLSIZE);
    if (d == NULL) {
        pDiskAbc->unlock_buf((struct buf *)handle);
        return ERR_PTR(struct directory *, EIO);
    }
    *handlep = handle;
    return (d + (idx % cldirs));
}

/*
 * dir_remove()
 *    Remove given node from its directory
 *
 * The node's storage is assumed to have already been freed
 */
void FatDir::
dir_remove(struct node *n)
{
    void *handle;
    struct directory *d;
    UInt32 slot = n->n_slot;

    if (n == p_Sharedata->procroot)
        return;

    /*
     * Get the directory entry and its handle
     */
    d = get_dirent(n->n_dir, slot, &handle);
    if (IS_ERR(d)) {
        DPRINTF(("ERROR:dir_remove: can't get directory\n"));
        return;
    }

    /*
     * Flag name as being deleted, mark the directory block
     * dirty.  Special case (of course) for root.
     */
    d->DIR_Name[0] = DN_DEL;
    ddirty(handle);
    dfree(handle);

    /*
     * Unhash node
     */
    if (n->n_type == T_DIR) {
        hash_delete(p_Sharedata->dirhash, n->n_clust->c_clust[0]);
    }
    else {
        hash_delete(n->n_dir->n_files, n->n_slot);
    }

    /*
     * Null out any leading VSE's
     */
    while (slot > 0) {
        UInt8 c;

        /*
         * Get next slot.  Leave when we can't get the slot,
         * or when it stops being leading VSE's.
         */
        slot -= 1;
        d = get_dirent(n->n_dir, slot, &handle);
        if (IS_ERR(d)) {
            break;
        }
        c = d->DIR_Name[0];
        ASSERT_DEBUG(c != 0, "dir_remove: null'ed entry");
        if ((c == DN_DEL) || (d->DIR_Attr != DA_VFAT)) {
            dfree(handle);
            break;
        }

        /*
         * Mark the slot deleted, and continue walking backwards
         */
        d->DIR_Name[0] = DN_DEL;
        ddirty(handle);
        dfree(handle);
    }
}

/*
 * dir_findslot()
 *    Find an open slot in the directory
 *
 * Returns a directory index on success, otherwise -1.
 */
int FatDir::
dir_findslot(struct node *n, int nentry)
{
    struct directory *base, *d, *endd;
    UInt32 x, start = 0, got = 0 ;
    UInt32 extclusts = 0;
    struct clust *c = n->n_clust;
    void *handle;
    UInt8 ch;

    if (n == p_Sharedata->rootdir) {
        /*
         * Root is a single linear scan
         */
        endd = rootdirents+p_Sharedata->dirents;
        for (d = rootdirents; d < endd;++d) {
            /*
             * When we find a deleted entry, include
             * it in our attempt to find "nentry" of
             * them in a row.
             */
            ch = d->DIR_Name[0];
            if ((ch == 0) || (ch == DN_DEL)) {
                /*
                 * First one--remember beginning of run
                 */
                if (got++== 0) {
                    start = d - rootdirents;
                }

                /*
                 * Got all we needed?  Return the
                 * needed one(s).
                 */
                if ((int)got == nentry) {
                    return(start);
                }
                /*
                 * Didn't find enough in a row--start over
                 */
            }
            else {
                got = 0;
            }
        }

        /*
         * Sorry.
         */
        return(-2);
    }

    /*
     * Search each existing cluster
     */
    for (x = 0; x < c->c_nclust;++x) {
        /*
         * Get next cluster of directory entries
         */
        handle = pDiskAbc->find_buf(BOFF(c->c_clust[x]), CLSIZE, ABC_FILL);
        if (IS_ERR(handle)) {
            return -1;
        }

        /*
         * Scan
         */
        base = d =
            (struct directory *)pDiskAbc->index_buf((struct buf *)handle,
                        0, CLSIZE);
        if (base == NULL) {
            pDiskAbc->unlock_buf((struct buf *)handle);
            return(-1);
        }
        endd = d+cldirs;
        for (; d < endd;++d) {
            ch = d->DIR_Name[0];
            if ((ch == 0) || (ch == DN_DEL)) {
                /*
                 * First one--remember beginning of run
                 */
                if (got++== 0) {
                    start = (d - base) + (x * cldirs);
                }

                /*
                 * Got all we needed?  Return the
                 * needed one(s).
                 */
                if ((int)got == nentry) {
                    pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);
                    return(start);
                }
                /*
                 * Didn't find enough in a row--start over
                 */
            }
            else {
                got = 0;
            }
        }
        pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);
    }

    /*
     * No free entries.  Try to extend.
     */
    extclusts = roundup(nentry, cldirs)/cldirs;
    if (pFat->clust_setlen(c, c->c_nclust + extclusts)) {
        /*
         * No more blocks
         */
        return(-1);
    }
    /*
     * Zero block, return pointer to base
     */
    handle = pDiskAbc->find_buf(BOFF(c->c_clust[c->c_nclust-1]),
        CLSIZE, ABC_FILL);
    ASSERT_DEBUG(!IS_ERR(handle), "dir_findslot: no handle on extend");

    d =(struct directory *)pDiskAbc->index_buf((struct buf *)handle, 0,
                    CLSIZE);
    if (d == NULL) {
        pDiskAbc->unlock_buf((struct buf *)handle);
        return(-1);
    }
    bzero(d, p_Sharedata->clsize);
    ddirty(handle);
    pDiskAbc->unlock_buf((struct buf *)(struct buf *)handle);
    if (start)
        return (start);
    else
        return(x * cldirs);
}

/*
 * unicode_set_chars()
 *    Do a run of characters in a buffer
 *
 * Return 1 if we see end-of-string, 0 otherwise.
 */
int
unicode_set_chars(UInt8 *ubuf, wchar_t *buf, UInt32 len)
{
    wchar_t c;
    UInt32 x;

    for (x = 0; x < len;++x) {
        c = *buf++;
        *ubuf++ = (UInt8)c;
        *ubuf++ = c >> 8;
        if (c == L'\0') {
            return(1);
        }
    }
    return(0);
}

/*
 * mymemset()
 *    Private memset()
 */
void
mymemset(UInt8 *buf, UInt8 fill, UInt32 size)
{
    UInt32 x;

    for (x = 0; x < size;++x) {
        *buf++ = fill;
    }
}

/*
 * unicode_set()
 *    Insert up to the next VSE_MAX_NAME charactersinto the "dv" entry
 */
void
unicode_set(struct dirVSE *dv, wchar_t *name)
{
    /*
     * Mickysoft seems to want trailing 0xFF chars.
     */
    mymemset(dv->dv_name1, 0xFF, VSE_1SIZE*2);
    mymemset(dv->dv_name2, 0xFF, VSE_2SIZE*2);
    mymemset(dv->dv_name3, 0xFF, VSE_3SIZE*2);

    /*
     * Fill in from string until string exhausted
     */
    if (unicode_set_chars(dv->dv_name1, name, VSE_1SIZE)) {
        return;
    }
    if (unicode_set_chars(dv->dv_name2, name + VSE_1SIZE, VSE_2SIZE)) {
        return;
    }
    (void)unicode_set_chars(dv->dv_name3,
        name+VSE_1SIZE+VSE_2SIZE, VSE_3SIZE);
}

/*
 * unique_filename()
 *    Create a unique filename within the directory at node "n"
 *
 * I've not gone to any particular trouble to exactly mimic W95's
 * behavior, but the results should always be legal under W95.
 *
 * The resulting filename is builtinto f1 (base name) and f2 (extension).
 */
void FatDir::
unique_filename(char *file, char *f1, char *f2, struct node *n,
                wchar_t *filename)
{
    char c, mod[9], *p;
    int x, baselen, len, tilde = 0;
    struct directory d;

    /*
     * Assemble the base, up to 8 characters
     */
    strcpy(f1, "        ");
    for (x = 0, p = f1; x < 8;++x) {
        /*
         * End of string?
         */
        c = file[x];
        if (c == '\0') {
            break;
        }

        /*
         * Map illegals to "_"
         */
        if (strchr(illegal, c)) {
            c = '_';
        }

        /*
         * If it's the first ".", we have the base of our name
         */
        if (c == '.') {
            /*
             * We map ".<name>"into "<name>~<num>"
             */
            if (x == 0) {
                tilde = 1;
                continue;
            }

            /*
             * We need a generated name if there's more
             * than one dot in the filename.
             */
            if (strchr(file + x + 1, '.')) {
                tilde = 1;
            }
            break;
        }

        /*
         * Bring it across
         */
        if (!isupper(c))
            *p++ = toupper(c);
        else
            *p++ = c;
    }
    baselen = p - f1;

    /*
     * We also need a generated name if the "real" name is too long
     */
    if (baselen == 8) {
        if (file[x] && (file[x] != '.')) {
            tilde = 1;
        }
    }

    /*
     * Assemble up to three chars from after the last dot in the
     * filename.
     */
    strcpy(f2, "   ");
    p = strrchr(file, '.');
    if (p) {
        for (++p, x = 0; x < 3;++x) {
            c = p[x];
            if (c == '\0') {
                break;
            }
            if (strchr(illegal, c)) {
                c = '_';
            }
            if (!isupper(c))
                f2[x] = toupper(c);
            else
                f2[x] = c;
        }

        /*
         * We also need a generated name if the extension is
         * too long.
         */
        if ((x == 3) && p[x]) {
            tilde = 1;
        }
    }

    /*
     * If there's no need to add a unique filename generation
     * to the filename, we can return now.
     */
    if (!tilde) {
        return;
    }

    /*
     * Now tack on a "~<number>" to the base filename, bumping
     * the number until we find an unused one.
     * TBD: this could be optimized quite a bit over this simple
     *  scan.
     */
    for (x = 1; ; x += 1) {
        /*
         * Generate the next name to try
         */
        (void)_sprintf(mod, "~%d", x);
        len = strlen(mod);
        if ((baselen + len) > 8) {
            bcopy(mod, f1 + (8 - len), len);
        }
        else {
            bcopy(mod, f1 + baselen, len);
        }

        /*
         * See if it can be found in the directory
         */
        x = node_search(n, f1, f2, &d, filename);
        if (x == -1) {
            return;
        }
    }
}

/*
 * dir_newfile()
 *    Create a directory entry. If success return pointer who point new
 * node, else return error code.
 *
 * For directories, we also allocate the first cluster and put its
 * "." and ".." entries in place.
 */
struct node * FatDir::
dir_newfile(class CFatFile *f, wchar_t *file,int isdir)
{
    struct directory *d, *dir;
    struct clust *c = NULL;
    void *handle, *dirhandle;
    char f1[9], f2[4], ochar0;
    int fLowercase = 0, x, slot=0, nslot=0, error = 0;
    UInt8 cksum;
    struct node *n = (struct node *)f->f_node;
    struct node *pNewNode = NULL;

    /*
     * Get a fat version of the name, put in place
     */

    char *GbFileName = NULL;
    wchar_t *fileName = NULL;

    if (wcslen(file) >= FULL_PATH_LEN)
        return ERR_PTR(struct node *, EINVAL);

    GbFileName = (char *)malloc(FULL_PATH_LEN * MB_LEN_MAX + 1);
    if (NULL == GbFileName) {
        return ERR_PTR(struct node *, ENOMEM);
    }

    fileName = (wchar_t *)malloc(sizeof(wchar_t) * FULL_PATH_LEN);
    if (NULL == fileName) {
        free(GbFileName);
        return ERR_PTR(struct node *, ENOMEM);
    }

    wcscpy(fileName, file);
    /*
     * Wipe all '.' and ' ' at end of filename
     */
    dir_treatFileName(fileName);

#ifndef _win32
    WcsToMbs(GbFileName, fileName, FULL_PATH_LEN * MB_LEN_MAX + 1);
#else
    wcstombs(GbFileName, fileName, FULL_PATH_LEN * MB_LEN_MAX + 1);
#endif

    switch (map_filename((char *)GbFileName, f1, f2)) {
    case 1:
        /*
         * Create a unique short filename entry
         */
        unique_filename((char *)GbFileName, f1, f2, n, fileName);

        /*
         * Allocate enough slots all in a row to hold the long
         * filename VSE's, along with the short filename entry.
         */
        nslot = roundup(wcslen(fileName), VSE_NAME_SIZE) / VSE_NAME_SIZE;
        slot = dir_findslot(n, nslot+1);
        if (slot < 0) {
            free(fileName);
            free(GbFileName);
            return ERR_PTR(struct node *, ((slot == -2) ? ENOENTRY : ENOSPC));
        }

        /*
         * Fill in the long filename VSE's.  We'll then dropinto
         * common code to create the short filename entry, along
         * with the rest of the file.
         */
        cksum = short_checksum(f1, f2);
        for (x = 0; x < nslot; ++x) {
            struct dirVSE *dv;

            /*
             * Note: we purposely location (x == 0) at the
             * last allocated slot.
             */
            dv = (struct dirVSE *)get_dirent(n,
                slot + ((nslot-1) - x), &handle);
            if (IS_ERR(dv)) {
                free(fileName);
                free(GbFileName);
                return ERR_PTR(struct node *, EIO);
            }
            bzero(dv, sizeof(struct dirVSE));
            dv->dv_attr = VSE_ATTR_VFAT;
            dv->dv_id = x+1;
            dv->dv_sum = cksum;
            unicode_set(dv, fileName + (x * VSE_NAME_SIZE));
            if (x == (nslot-1)) {
                dv->dv_id |= VSE_ID_LAST;
            }
            ddirty(handle);
            dfree(handle);
        }

        /*
         * Leave "slot" at the slot for the short filename.
         */
        slot += nslot;
        break;

    case 0:
        /*
         * For uppercase short filenames, only need the single entry
         */
        slot = dir_findslot(n, 1);
        if (slot < 0) {
            free(fileName);
            free(GbFileName);
            return ERR_PTR(struct node *, ((slot == -2) ? ENOENTRY : ENOSPC));
        }
        break;

    case 3:
        /*
         * For lowercase short filenames, only need the single entry
         */
        fLowercase = 1;
        slot = dir_findslot(n, 1);
        if (slot < 0) {
            free(fileName);
            free(GbFileName);
            return ERR_PTR(struct node *, ((slot == -2) ? ENOENTRY : ENOSPC));
        }
        break;

   case 2:
        /*
         * Invalid filename
         */
        free(fileName);
        free(GbFileName);
        return ERR_PTR(struct node *, EINVAL);
    default:
        /* Should not come here */
        ASSERT_DEBUG(0, "dir_newfile: invalid file parse");
        free(fileName);
        free(GbFileName);
        return ERR_PTR(struct node *, EAGAIN);
    }

    free(fileName);
    free(GbFileName);

    /*
     * Access the slot
     */
    dir = get_dirent(n, slot, &dirhandle);
    if (IS_ERR(dir)) {
        /*
         * Sorry...
         */
        return ERR_PTR(struct node *, dir);
    }
    ochar0 = dir->DIR_Name[0];    /* In case we have to undo this */

    memset(dir, 0, sizeof(struct directory));

    bcopy(f1, dir->DIR_Name, sizeof(dir->DIR_Name));
    bcopy(f2, dir->DIR_Ext, sizeof(dir->DIR_Ext));
    dir->DIR_Attr = 0;
    /*
     *Have to set uppercase or lowercase
     */
    if(fLowercase)
        dir->DIR_NTRes |= RES_LOWCASE;
    if (isdir & ACC_READONLY) {
        dir->DIR_Attr |= DA_READONLY;
    }
    SETSTART(dir, 0);
    dir->DIR_FileSize = 0;
    time_init(dir);

    /*
     * Have to allocate an empty directory (., ..) for directories
     */
    if (isdir & ACC_DIR) {
        /*
         * Get a single cluster
         */
        c = pFat->alloc_clust(0);
        if (c == 0) {
            error = ENOMEM;
            goto out;
        }
        if (pFat->clust_setlen(c, 1)) {
            free(c);
            error = ENOSPC;
            goto out;
        }

        /*
         * Put "." first, ".." second, and zero out the rest
         */
        handle = pDiskAbc->find_buf(BOFF(c->c_clust[0]), CLSIZE, 0);
        if (IS_ERR(handle))
            return ERR_PTR(struct node *, PTR_ERR(handle));

        d = (struct directory *)pDiskAbc->index_buf((struct buf *)handle,
                                                    0, CLSIZE);
        if (d == NULL) {
            pDiskAbc->unlock_buf((struct buf *)handle);
            return ERR_PTR(struct node *, EIO);
        }
        bzero(d, p_Sharedata->clsize);
        bcopy(".       ", d->DIR_Name, sizeof(d->DIR_Name));
        bcopy("   ", d->DIR_Ext, sizeof(d->DIR_Ext));
        dir->DIR_Attr |= DA_DIR;
        d->DIR_Attr = DA_DIR;
        SETSTART(dir, c->c_clust[0]);
        SETSTART(d, c->c_clust[0]);
        timestamp(dir, 0);
        time_init(d);
        ++d;
        bcopy("..      ", d->DIR_Name, sizeof(d->DIR_Name));
        bcopy("   ", d->DIR_Ext, sizeof(d->DIR_Ext));
        d->DIR_Attr = DA_DIR;
        timestamp(dir, 0);
        time_init(d);
        if (n == p_Sharedata->rootdir) {
            SETSTART(d, 0);
        }
        else {
            SETSTART(d, n->n_clust->c_clust[0]);
        }
        ddirty(handle);
        dfree(handle);
    }

    pNewNode = dir_getNodeByDirEntry(n, *dir, slot);
    if (!IS_ERR(pNewNode)) {
        pNewNode->n_flags |= N_DIRTY;
    }

out:
    if (error) {
        dir->DIR_Name[0] = DN_DEL;
        while (slot > 0) {
            UInt8 c;

            slot -= 1;
            d = get_dirent(n, slot, &handle);
            if (IS_ERR(d)) {
                break;
            }
            c = d->DIR_Name[0];
            ASSERT_DEBUG(c != 0, "dir_remove: null'ed entry");
            if ((c == DN_DEL) || (d->DIR_Attr != DA_VFAT)) {
                dfree(handle);
                break;
            }
            d->DIR_Name[0] = DN_DEL;
            ddirty(handle);
            dfree(handle);
        }
    }
    else {
        ddirty(dirhandle);
        n->n_flags |= N_DIRTY;
    }
    if (dirhandle) {
        pDiskAbc->unlock_buf((struct buf *)(struct buf *)dirhandle);
    }
    if (error) {
        return ERR_PTR(struct node *, error);
    }

    if (isdir & ACC_DIR) {
        pFat->free_clust(c);
    }
    return(pNewNode);
}

/*
 * dir_getNodeByDirEntry()
 *    Create node and initialize it by directory entry
 */

struct node * FatDir::
dir_getNodeByDirEntry(struct node *pParentNode, struct directory &d, int nSlot)
{
    struct node *pNode;

    /*
     * Need to create a new node.  Toss it together.
     */
    pNode = (struct node *) malloc(sizeof(struct node));
    if (NULL == pNode) {
        return ERR_PTR(struct node *, ENOMEM);
    }
    pNode->n_type = map_type(d.DIR_Attr);

    /*
     * Root dir of a FAT-32 filesystem has a cluster number of 0
     */

    pNode->n_clust = pFat->alloc_clust(&d);
    if (pNode->n_clust == 0) {
        free(pNode);
        return ERR_PTR(struct node *, ENOMEM);
    }

    pNode->n_mode = ACC_READ |
        ((d.DIR_Attr & DA_READONLY) ? 0 : ACC_WRITE);
    pNode->n_sharemode = FileShareMode_Write;
    pNode->n_firsthandle = 0;

    pNode->n_refs = 0;
    pNode->n_flags = 0;    /* Not dirty to start */
    pNode->n_dir = pParentNode;
    ref_node(pNode);
    pNode->n_slot = nSlot;
    pNode->n_inum = get_inum(pParentNode, nSlot);
    Ktfs_Init_Lock(&pNode->n_lock);

    if (pNode->n_type == T_DIR) {
        /*
         * Get the file hash for directories
         */
        pNode->n_files = hash_alloc(8);
        if (pNode->n_files == 0) {
            pFat->free_clust(pNode->n_clust);
            Ktfs_Term_Lock(&pNode->n_lock);
            free(pNode);
            return ERR_PTR(struct node *, ENOMEM);
        }

        if (hash_insert(p_Sharedata->dirhash, START(&d), pNode)) {
            hash_dealloc(pNode->n_files);
            pFat->free_clust(pNode->n_clust);
             Ktfs_Term_Lock(&pNode->n_lock);
            free(pNode);
            return ERR_PTR(struct node *, ENOMEM);
        }
    }
    else {
        /*
         * Fill in directory slot information for files
         */
        pNode->n_len = d.DIR_FileSize;
        /*
         * We should compare file len and clust list bytes in case
         * of FAT wrong.
         */
        UInt32 clustListBytes = pNode->n_clust->c_nclust*p_Sharedata->clsize;

        if (pNode->n_len > clustListBytes) {
            DPRINTF(("WARNING: file len %d bytes, clustors len %d bytes\n",
                pNode->n_len, clustListBytes));

            pNode->n_len = clustListBytes;
            dir_setlen(pNode);
            pNode->n_flags |= N_DIRTY;
        }
        else if (roundup(pNode->n_len, p_Sharedata->clsize) < clustListBytes) {
            DPRINTF(("WARNING: file len %d bytes, clustors len %d bytes\n",
                pNode->n_len, clustListBytes));

            pFat->clust_setlen(pNode->n_clust,
                (pNode->n_len+p_Sharedata->clsize-1)/p_Sharedata->clsize);
            dir_setlen(pNode);
            pNode->n_flags |= N_DIRTY;
        }

        if (hash_insert(pParentNode->n_files, nSlot, pNode)) {
            pFat->free_clust(pNode->n_clust);
            Ktfs_Term_Lock(&pNode->n_lock);
            free(pNode);
            return ERR_PTR(struct node *, ENOMEM);
        }
    }

    return(pNode);
}

/*
 * dir_copy()
 *    Get a snapshot of a directory entry
 */
int FatDir::
dir_copy(struct node *n, UInt32 pos, struct directory *dp)
{
    struct directory *d;
    void *handle;

    /*
     * Get pointer to slot
     */
    d = get_dirent(n, pos, &handle);

    /*
     * If we got one, copy out its contents.  Free real buffer
     * once we have the copy.q
     */
    if (!IS_ERR(d)) {
        *dp = *d;
        dfree(handle);
    }

    /*
     * Return error indication
     */
    if (IS_ERR(d)) {
        return PTR_ERR(d);
    }
    else {
        return 0;
    }
}

/*
 * time_init()
 *    Mark create date/time onto given dir entry
 */
void FatDir::
time_init(struct directory *d)
{
    timeval_o timev;
    timespec_o ts;

    DzGetSystemTime((SystemTime*)&timev);
    TIMEVAL_TO_TIMESPEC(&timev, &ts);

    unix2dostime(&ts, &(d->DIR_CrtDate)
        , &(d->DIR_CrtTime), &(d->DIR_CrtTimeTenth));

    ts.tv_sec++;          //   just for logical correctness
    unix2dostime(&ts, &(d->DIR_WrtDate), &(d->DIR_WrtTime), NULL);

    unix2dostime(&ts, &(d->DIR_LstAccDate), NULL, NULL);
}

/*
 * time_access()
 *    Mark current(access) date onto given dir entry
 */
void FatDir::
time_access(struct directory *d, time_t t)
{
    timeval_o timev;
    timespec_o ts;

    if (t == 0) {
        /*
         * Get date/time from system, convert to struct tm so we
         * can pick apart day, month, etc.  Just zero date/time
         * if we can't get it.
         */
        DzGetSystemTime((SystemTime*)&timev);
        TIMEVAL_TO_TIMESPEC(&timev, &ts);
    }
    else {
        ts.tv_sec = t;
        ts.tv_nsec = 0;
    }

    unix2dostime(&ts, &(d->DIR_LstAccDate), NULL, NULL);
}

/*
 * dir_timeaccess()
 *    Set access timestamp on a file
 *
 * Just gets dir entry field and uses time_access()
 */

void FatDir::
dir_timeaccess(struct node *n, time_t t)
{
    struct directory *d;
    void *handle;

    if (n == p_Sharedata->procroot)
        return;

    /*
     * Access dir entry
     */
    d = get_dirent(n->n_dir, n->n_slot, &handle);
    ASSERT_DEBUG(!IS_ERR(d), "dir_timestamp: lost handle");

    /*
     * Stamp it with the requested time
     */
    time_access(d, t);

    /*
     * Mark it modified
     */
    ddirty(handle);
    dfree(handle);
    n->n_flags |= N_DIRTY;
}

/*
 * timestamp()
 *    Mark current(modification) date/time onto given dir entry
 */
void FatDir::
timestamp(struct directory *d, time_t t)
{
    timeval_o timev;
    timespec_o ts;

    if (t == 0) {
        /*
         * Get date/time from system, convert to struct tm so we
         * can pick apart day, month, etc.  Just zero date/time
         * if we can't get it.
         */
        DzGetSystemTime((SystemTime*)&timev);
        TIMEVAL_TO_TIMESPEC(&timev, &ts);
    }
    else {
        ts.tv_sec = t;
        ts.tv_nsec = 0;
    }

    unix2dostime(&ts, &(d->DIR_WrtDate), &(d->DIR_WrtTime), NULL);
}

/*
 * dir_timestamp()
 *    Set timestamp on a file
 *
 * Just gets dir entry field and uses timestamp()
 */
void FatDir::
dir_timestamp(struct node *n, time_t t)
{
    struct directory *d;
    void *handle;

    /*
     * Access dir entry
     */
    d = get_dirent(n->n_dir, n->n_slot, &handle);
    ASSERT_DEBUG(!IS_ERR(d), "dir_timestamp: lost handle");

    /*
     * Stamp it with the requested time
     */
    timestamp(d, t);

    /*
     * Mark it modified
     */
    ddirty(handle);
    dfree(handle);
    n->n_flags |= N_DIRTY;
}

/*
 * dir_setlen()
 *    Move the length attribute from the nodeinto the dir entry
 *
 * Used on last close to update the directory entry.  It's not worth
 * scribbling the directory block in theinterim.
 * Return 0 on success, error code on failed.
 */
int FatDir::
dir_setlen(struct node *n)
{
    struct directory *d;
    void *handle;
    struct clust *c = n->n_clust;
    bool dirty = false;

    /*
     * Non-issue unless it's a file
     */
    if (n->n_type != T_FILE) {
        return 0;
    }

    /*
     * Get pointer to slot
     */
    d = get_dirent(n->n_dir, n->n_slot, &handle);
    if (IS_ERR(d))
        return PTR_ERR(d);

    /*
     * Update it, mark the block dirty, log change time, and return
     */
    if (d->DIR_FileSize != n->n_len) {
        d->DIR_FileSize = n->n_len;

        if (c->c_nclust) {
            ASSERT_DEBUG(d->DIR_FileSize, "dir_setlen: !len clust");
            SETSTART(d, c->c_clust[0]);
        }
        else {
            ASSERT_DEBUG(d->DIR_FileSize == 0, "dir_setlen: len !clust");
            SETSTART(d, 0);
        }
        dirty = true;
    }
    else if ((c->c_nclust > 0) && ((UInt32)START(d) != c->c_clust[0])) {
        SETSTART(d, c->c_clust[0]);
        dirty = true;
    }
    else if ((c->c_nclust == 0) && ((UInt32)START(d) != 0)) {
        SETSTART(d, 0);
        dirty = true;
    }

    if (dirty) {
        timestamp(d, 0);
        ddirty(handle);
    }
    dfree(handle);

    return 0;
}

/*
 * root_sync()
 *    If root directory is modified, flush to disk
 */
void FatDir::
root_sync(void)
{
    if (!root_dirty) {
        return;
    }

    UInt64 uOffset = (p_Sharedata->bootb.BPB_ResvdSecCnt+ \
                        p_Sharedata->bootb.BPB_NumFATs * \
                        p_Sharedata->bootb.BPB_FATSz16+ \
                        p_Sharedata->bpb_base) * p_Sharedata->secsz;

    ECode ec = WriteDevice(
                p_Sharedata->pBlkDev, uOffset, rootsize,
                (PByte)rootdirents, NULL, NULL);
    if (FAILED(ec)) {
        DPRINTF(("ERROR: Failed to Write from the offset:%d", uOffset));
        return;
    }

    root_dirty = 0;
}

/*
 * tree_contains()
 *    Tell if one dir contains another node within its directory tree
 */
int FatDir::
tree_contains(struct node *tree, struct node *n)
{
    struct node *nprev;

    /*
     * Walk the tree upwards
     */
    while (n != tree) {
        /*
         * Look up next node upwards.  Done when get same value
         * (loop at root) or NULL (top of FAT-32 root)
         */
        nprev = n;
        n = dir_look(nprev, L"..");
        if (IS_ERR(n) || (n == nprev)) {
            return(0);
        }

        /*
         * If we runinto our root, return true
         */
        deref_node(n);
        if (n == tree) {
            return(1);
        }
    }

    /*
     * If we find the node on the way up, this is a no-no
     */
    return(1);
}

/*
 * fix_dotdot()
 *    Update the ".." entry in a node for a new value
 */
void FatDir::
fix_dotdot(struct node *n, struct node *ndir)
{
    struct directory *de;
    void *handle;
    struct buf *b;

    /*
     * Get the entry, convert the handle backinto a buf pointer.
     * Index 0 for a dir is always "."; 1 is always "..".
     */
    de = get_dirent(n, 1, &handle);
    if (IS_ERR(de)) {
        return;
    }
    b =(struct buf *) handle;

    /*
     * Point to new parent dir
     */
    if ((ndir != p_Sharedata->rootdir) &&
        (ndir != p_Sharedata->procroot)) {
        SETSTART(de, pFat->get_clust(ndir->n_clust, 0));
    }
    ddirty(b);
    dfree(handle);
}

/*
 * check_busy()
 *    See if the given node's busy with a page cache reference
 */
int FatDir::
check_busy(struct node *n)
{
    if (n->n_refs > 1) {
        if (n->n_flags & N_FID) {
            n->n_flags &= ~N_FID;
            //deref_node(n);
            return(EAGAIN);
        }
        return EBUSY;
    }
    return(0);
}

/*
 * do_rename()
 *    Given open directories and filenames, rename an entry
 *
 * Returns an error string or 0 for success.
 */
int FatDir::
do_rename(class CFatFile *fsrc, wchar_t *src, class CFatFile *fdest,
                wchar_t *dest)
{
    struct node *ndsrc, *nddest, *nsrc, *ndest;
    struct directory *dsrc, *ddest, dtmp;
    void *handsrc, *handdest;
    char name[8], ext[3];
    int p;
    /*
     * Get directory nodes, verify that they're directories
     */
    ndsrc = fsrc->f_node;
    nddest = fdest->f_node;
    if ((ndsrc->n_type != T_DIR) || (nddest->n_type != T_DIR)) {
        return(ENOTDIR);
    }
    /*
     * Look up each file within the directories
     */
    nsrc = dir_look(ndsrc, src);
    if (IS_ERR(nsrc)) {
        return (PTR_ERR(nsrc));
    }

    Ktfs_Lock(&nsrc->n_lock);

    /*
     * If we're moving a directory, make sure we're not moving to
     * a point within our own tree
     */
    if ((nsrc->n_type == T_DIR) && tree_contains(nsrc, nddest)) {
        Ktfs_Unlock(&nsrc->n_lock);
        deref_node(nsrc);
        return(EINVAL);
    }

    /*
     * Make sure we're the only ones using this file
     */
    if ((p = check_busy(nsrc))) {
        Ktfs_Unlock(&nsrc->n_lock);
        deref_node(nsrc);
        return(p);
    }

    /*
     * NOTICE:We allowed rename a to A in same directory.
     */
    ndest = dir_look(nddest, dest);
    if (!IS_ERR(ndest) && (nddest != ndsrc || nsrc != ndest)) {
        Ktfs_Unlock(&nsrc->n_lock);
        deref_node(nsrc);
        deref_node(ndest);
        return (EEXIST);
    }
    else {
        /*
         * In this case, we did not found destination or
         * found but in same directory with different name,
         * for example, rename a to A in same directory.
         * Otherwise, create a new entry for the file
         */
        if (!IS_ERR(ndest)) {
            // Just rename a to A in same directory.
            deref_node(ndest);
            ndest = NULL;
        }
        ndest = dir_newfile(fdest, dest, 0);
        if (IS_ERR(ndest)) {
            deref_node(nsrc);
            return PTR_ERR(ndest);
        }
    }

    Ktfs_Lock(&ndest->n_lock);

    /*
     * Ok... we have our source and destination.  Swap the
     * directory attributes (but not the names and NTRes).
     */
    dsrc = get_dirent(nsrc->n_dir, nsrc->n_slot, &handsrc);
    assert(!IS_ERR(dsrc));

    ddest = get_dirent(ndest->n_dir, ndest->n_slot, &handdest);
    assert(!IS_ERR(ddest));

    dtmp = *ddest;
    *ddest = *dsrc;
    bcopy(dtmp.DIR_Name, ddest->DIR_Name, 8);
    bcopy(dtmp.DIR_Ext, ddest->DIR_Ext, 3);
    bcopy(dsrc->DIR_Name, name, 8);
    bcopy(dsrc->DIR_Ext, ext, 3);
    *dsrc = dtmp;
    bcopy(name, dsrc->DIR_Name, 8);
    bcopy(ext, dsrc->DIR_Ext, 3);

    dsrc->DIR_NTRes = ddest->DIR_NTRes;
    ddest->DIR_NTRes = dtmp.DIR_NTRes;

    /*
     * Mark dir blocks containing entries as dirty.  Root dir
     * has a NULL handle; root_dirty gets flagged instead.
     */
    ddirty(handsrc);
    dfree(handsrc);
    ddirty(handdest);
    dfree(handdest);

    /*
     * File nodes are keyed under their parent dir.  Since this
     * has changed, move them about.
     */
    if (nsrc->n_type == T_FILE) {
        /*
         * Unhash each node.  Re-hash it under its new parent.
         * Node pointers and all are swapped a little later.
         */
        hash_delete(ndsrc->n_files, nsrc->n_slot);
        hash_delete(nddest->n_files, ndest->n_slot);
        ASSERT_DEBUG(hash_insert(ndsrc->n_files, nsrc->n_slot, ndest) == 0,
            "do_rename: hash1 failed");
        ASSERT_DEBUG(hash_insert(nddest->n_files, ndest->n_slot, nsrc) == 0,
            "do_rename: hash2 failed");
    }
    else {
        /*
         * Dir nodes are hashed under starting sector,
         * which should not change.  Fix up ".." for its
         * new parent dir.
         */
        hash_delete(nddest->n_files, ndest->n_slot);
        ASSERT_DEBUG(hash_insert(ndsrc->n_files, nsrc->n_slot, ndest) == 0,
            "do_rename: hash1 failed");
        fix_dotdot(nsrc, nddest);
    }

    /*
     * Now swap the node information on what directory
     * entry it occupies.
     */
    {
        struct node *ntmp;
        UInt32 tmp;
        UInt32 inum;

        /*
         * The dir entries
         */
        ntmp = nsrc->n_dir;
        nsrc->n_dir = ndest->n_dir;
        ndest->n_dir = ntmp;

        /*
         * Slot # (since we're now referenced by the
         * other guy's directory/directory-slot)
         */
        tmp = nsrc->n_slot;
        nsrc->n_slot = ndest->n_slot;
        ndest->n_slot = tmp;

        /*
         * And inode number (derived from dir & slot)
         */
        inum = nsrc->n_inum;
        nsrc->n_inum = ndest->n_inum;
        ndest->n_inum = inum;
    }

    Ktfs_Unlock(&nsrc->n_lock);

    /*
     * Remove the old filename entry (which actually now resides
     * in the source filename's directory slot).
     */
    dir_remove(ndest);

    /*
     * Release references to the nodes we have fiddled.  The
     * directory nodes are help open by our client, who will
     * close them on his own.
     */

    Ktfs_Unlock(&ndest->n_lock);

    deref_node(ndest);
    deref_node(nsrc);

    /*
     * Flush dir entries
     */
    ndsrc->n_flags |= N_DIRTY;
    nddest->n_flags |= N_DIRTY;
    nsrc->n_flags |= N_DEL;

    sync();

    /*
     * Success
     */
    return(0);
}

int valid_fname(char *buf, int bufsize)
{
    if (bufsize >= MAX_FILE_NAME) {
        return(0);
    }
    if (buf[bufsize-1] != '\0') {
        return(0);
    }
    return(1);
}

/*
 * fat_rename()
 *    Rename one dir entry to another
 *
 * For move of directory, just protect against cycles and update
 * the ".." entry in the directory after the move.
 */
int FatDir::
fatfs_rename(class CFatFile *fsrc, wchar_t *src,
    class CFatFile *fdest, wchar_t *dest)
{
    int errno;
    errno = do_rename(fsrc, src, fdest, dest);
    return errno;
}

/*
 * cancel_rename()
 *    A client exit()'ed before completing a rename.  Clean up.
 */
void FatDir::
cancel_rename(class CFatFile *f)
{
    hash_delete(rename_pending, f->f_rename_id);
    f->f_rename_id = 0;
}

/*
 * dir_set_type()
 *    Set type of entry
 *
 * Only switching between symlinks and files is allowed.  Returns 1
 * on error, 0 on success.
 */
int FatDir::
dir_set_type(class CFatFile *f, char *newtype)
{
    void *handle;
    struct directory *d;
    struct node *n = f->f_node;
    int ntype;

    /*
     * Make sure desired type is recognized
     */
    if (!strcmp(newtype, "f")) {
        ntype = T_FILE;
    }
    else if (!strcmp(newtype, "symlink")) {
        ntype = T_SYM;
    }
    else {
        return(1);
    }

    /*
     * If type is not changing, return success on no-op
     */
    if ((int)n->n_type == ntype) {
        return(0);
    }

    /*
     * Make sure node is either symlink or file
     */
    if ((n->n_type != T_FILE) && (n->n_type != T_DIR)) {
        return(1);
    }

    /*
     * Access dir slot
     */
    d = get_dirent(n->n_dir, n->n_slot, &handle);
    ASSERT_DEBUG(!IS_ERR(d), "dir_set_type: lost handle");

    /*
     * Set type of node
     */
    n->n_type = ntype;

    /*
     * Update fat version of information in directory node.  Note our
     * slimy use of HIDDEN to mean symlink.
     */
    if (ntype == T_SYM) {
        d->DIR_Attr |= DA_HIDDEN;
    }
    else {
        d->DIR_Attr &= ~DA_HIDDEN;
    }

    /*
     * Flag dir entry as needing a flush, and return success
     */
    ddirty(handle);
    dfree(handle);
    n->n_flags |= N_DIRTY;
    return(0);
}

/*
 * dir_readonly()
 *    Set/clear dir readonly bit
 */
void FatDir::
dir_readonly(class CFatFile *f, int readonly)
{
    struct directory *d;
    void *handle;
    struct node *n = f->f_node;

    if (n == p_Sharedata->procroot)
        return;

    /*
     * Flag bit in dir entry header and cache of
     * it in n_mode.  Don't dirty anything if the
     * protection isn't changing.
     */
    d = get_dirent(n->n_dir, n->n_slot, &handle);
    if (IS_ERR(d))
        return;
    if (readonly) {
        d->DIR_Attr |= DA_READONLY;
        n->n_mode &= ~ACC_WRITE;
    }
    else {
        d->DIR_Attr &= ~DA_READONLY;
        n->n_mode |= ACC_WRITE;
    }
    ddirty(handle);
    dfree(handle);
    n->n_flags |= N_DIRTY;
}

void FatDir::
dir_treatFileName(wchar_t *fileName)
{
    wchar_t *p;
    /*
     * Wipe all '.' and ' ' at end of filename
     */
    p = fileName;
    p += wcslen(fileName) - 1;

    while ((p >= fileName) && ((L' ' == *p) || (L'.' == *p))) {
        *p = L'\0';
        p--;
    }
}
